(($) => {
  const route = { name: null, path: null, component: null };
  class Router {
    constructor() {
      return this;
    }
    createRouter(ops) {
      for (let n in ops) {
        switch (n) {
          case "routes":
            this.routes = ops[n];
            break;
          case "mode":
            break;
        }
      }
      const self = this;
      return {
        afterEach(fn) {
          self.afterEachFn = () => fn(self.currentRoute, self.preRoute);
          return this;
        },
        beforeEach(fn) {
          self.beforeEachFn = (callback) =>
            fn(self.currentRoute, self.preRoute, (ops) => {
              callback();
            });
          return this;
        },
        router: self,
        route: self.currentRoute,
      };
    }
    preRoute = route;
    currentRoute = route;
  }
  $.Router = Router;
  typeof define === "function" &&
    define("VTRouter", (require, exports, module) => {
      exports.Router = Router;
    });
})(this);

function useRouter(dom) {
  const findRoute = (routes, to) => {
    routes = routes.filter((item) =>
      typeof to === "string"
        ? item.name === to ||
          item.path === (/\?/.test(to) ? to.split("?")[0] : to)
        : item.name === to.name || item.path === to.path
    );
    routes[0] && to.params && (routes[0].params = to.params);
    routes[0] &&
      /\?/.test(to) &&
      (routes[0].query = array2Object(to.split("?")[1].split("&")));
    return routes;
  };
  const array2Object = (arr) => {
    let obj = {};
    arr.forEach(
      (item) => (
        (item = item.split("=")),
        (obj[item[0]] = new Function("return " + item[1])())
      )
    );
    return obj;
  };
  const object2String = (obj, space) => {
    let str = [];
    for (let n in obj) {
      str.push(n + "=${obj.query." + n + "}");
    }
    return str.join(space);
  };
  const parseURI = (obj) => {
    let uri = /\:[^\/]+/.test(obj.path)
      ? eval(
          "`" +
            obj.path.replace(/\:[^\/]+/gim, (a, b) => {
              a = a.replace(/\:/gim, "${obj.params.") + "}";
              return a;
            }) +
            "`"
        )
      : obj.query
      ? eval("`" + [obj.path, "?", object2String(obj.query)].join("") + "`")
      : obj.path;
    return uri;
  };

  class RouterView extends dom.customElement {
    constructor() {
      super();
      return this;
    }
    name = "routerView";
    connectedCallback() {
      this.bindDefault();
      const layout = document.createElement("div");
      layout.setAttribute("class", "router-view");
      const template = document.createElement("template");
      template.innerHTML = `<slot></slot>`;
      const templateContent = template.content;
      layout.append(templateContent.cloneNode(true));
      const shadowRoot = this.createShadowRoot();
      shadowRoot.append(layout);
    }
  }
  class RouterLink extends dom.customElement {
    constructor() {
      super();
      return this;
    }
    name = "routerLink";
    connectedCallback() {
      const self = this;
      this.bindDefault();
      const template = document.createElement("template");
      template.innerHTML = `<slot></slot>`;
      const templateContent = template.content;
      const link = document.createElement("a");

      const routes = findRoute(dom.$router.routes, this.to);
      routes.length > 0
        ? (link.href = parseURI(routes[0]))
        : (link.href = "javascript:;");

      link.append(templateContent.cloneNode(true));
      link.setAttribute("class", "es_router_link");
      link.addEventListener("click", function (e) {
        e.preventDefault();
        if (/\$router\./.test(self.to))
          new Function("dom", "dom." + self.to)(dom);
        else goRoute(routes);
      });
      const shadowRoot = this.createShadowRoot();
      shadowRoot.append(link);
    }
  }
  dom.component("router-view", RouterView);
  dom.component("router-link", RouterLink);

  dom.$router.goBack = (n) => {
    history.back(n || -1);
    hashGo();
  };
  dom.$router.goPrev = (n) => {
    history.forward(n || 1);
    hashGo();
  };
  dom.$router.push = (ops) => {
    const routes = dom.$router.routes.filter(
      (item) => item.name === ops.name || item.path === ops.path
    );
    if (routes.length > 0) {
      extend(routes[0], ops);
      goRoute(routes);
    }
  };

  const goRoute = (routes) => {
    if (routes.length > 0 && routes[0].name) {
      const router = dom.$router;
      router.beforeEachFn((route) => {
        route = route || routes[0];
        router.preRoute = router.currentRoute;
        router.currentRoute = route;
        location.hash = parseURI(route);
        const res = route.component();
        if (res) {
          const target = document.querySelector("router-view");
          target.childNodes.forEach((item) => target.removeChild(item));
          dom.render("router-view", res.template);
        }
        router.afterEachFn();
      });
    }
  };

  const hashGo = () => {
    const hash = location.hash;
    const routes = dom.$router.routes.filter((item) =>
      hash === ""
        ? item.path === "/"
        : /\?/.test(hash)
        ? item.path === hash.replace("#", "").split("?")[0]
        : parseURI(item) === hash.replace("#", "")
    );
    if (routes.length > 0) {
      goRoute(routes);
    }
  };
  hashGo();
}
